﻿/********************************************************************************

** 作者： XmPlatform

** 描述：XmHttpServer

*********************************************************************************/
using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Collections;
using System.Globalization;

namespace XmHttpServer.Rest
{
    /// <summary>
    /// rest请求的核心类，用于转换，以便于反射调用
    /// </summary>
    public class TypeConversion
    {
        private static IDictionary<Type, TypeConverter> customConverters;
        static TypeConversion()
        {
            customConverters = new Dictionary<Type, TypeConverter>();
            AddConverter(typeof(System.Xml.XmlDocument), new XmlDocumentConverter());
            //TODO: Add for external converters...
        }
        public static void AddConverter(Type forType, TypeConverter conv)
        {
            //Assert.NotNull(forType, "forType");
            //Assert.NotNull(conv, "conv");
            customConverters.Add(forType, conv);
        }


        public static object ConvertType(string propName, object valueToSet, Type typeToSet)
        {
            if (valueToSet != null)
            {
                if (IsAssignableFrom(valueToSet, typeToSet))
                {
                    return valueToSet;
                }
                if (typeToSet != null && typeToSet.IsArray)
                {
                    Type componentType = typeToSet.GetElementType();
                    if (valueToSet is IList)
                    {
                        IList elements = (IList)valueToSet;
                        return ToArray(componentType, elements, propName);
                    }
                    else if (valueToSet is string)
                    {
                        if (typeToSet.Equals(typeof(char[])))
                        {
                            return ((string)valueToSet).ToCharArray();
                        }
                        else
                        {
                            string[] elements = GetStringList((string)valueToSet);
                            return ToArray(componentType, elements, propName);
                        }
                    }
                    else if (!valueToSet.GetType().IsArray)
                    {
                        Array result = Array.CreateInstance(componentType, 1);
                        object val = ConvertType(propName, valueToSet, componentType);
                        result.SetValue(val, 0);
                        return result;
                    }
                }

                #region Type Converter Factory
                try
                {
                    TypeConverter typeConverter = GetTypeConverter(typeToSet);
                    if (typeConverter != null && typeConverter.CanConvertFrom(valueToSet.GetType()))
                    {
                        try
                        {
                            valueToSet = typeConverter.ConvertFrom(valueToSet);
                        }
                        catch
                        {
                            if (valueToSet is string)
                            {
                                valueToSet = typeConverter.ConvertFromInvariantString((string)valueToSet);
                            }
                        }
                    }
                    else
                    {
                        typeConverter = GetTypeConverter(valueToSet.GetType());
                        if (typeConverter != null && typeConverter.CanConvertTo(typeToSet))
                        {
                            valueToSet = typeConverter.ConvertTo(valueToSet, typeToSet);
                        }
                        else if (customConverters.ContainsKey(typeToSet) && customConverters[typeToSet].CanConvertFrom(valueToSet.GetType()))
                        {
                            valueToSet = customConverters[typeToSet].ConvertFrom(valueToSet);
                        }
                        else
                        {
                            // finally look if it's an enum
                            if (typeToSet != null
                                && typeToSet.IsEnum
                                && (!(valueToSet is float)
                                    && (!(valueToSet is double))))
                            {
                                // convert numeric value into enum's underlying type
                                Type numericType = Enum.GetUnderlyingType(typeToSet);
                                valueToSet = System.Convert.ChangeType(valueToSet, numericType);

                                if (Enum.IsDefined(typeToSet, valueToSet))
                                {
                                    valueToSet = Enum.ToObject(typeToSet, valueToSet);
                                }
                                else
                                {
                                    throw new InvalidCastException(string.Format("Property [{0}] could not be set. Type mismatch detected.", propName));
                                }
                            }
                            else
                            {
                                throw new InvalidCastException(string.Format("Property [{0}] could not be set. Type mismatch detected.", propName));
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    throw new InvalidCastException(string.Format("Property [{0}] could not be set. Type mismatch detected.", propName), ex);

                }
                #endregion
                if (valueToSet == null)
                {
                    throw new InvalidCastException(string.Format("Property [{0}] could not be set. Type mismatch detected.", propName));
                }
            }
            return valueToSet;
        }
        private static TypeConverter GetTypeConverter(Type type)
        {
            if (type == null) throw new ArgumentNullException("type"); 
            //Assert.NotNull(type, "type");
            TypeConverter converter = null;
            if (converter == null)
            {
                if (type.IsEnum)
                {
                    converter = new EnumConverter(type);
                }
                else
                {
                    converter = TypeDescriptor.GetConverter(type);
                }
            }

            return converter;
        }
        private static string[] GetStringList(string p)
        {
            return ToStringArray(p, ",");
        }
        public static string[] ToStringArray(string input, string delimiter)
        {
            if (input == null)
            {
                return new string[0];
            }
            if (string.IsNullOrEmpty(delimiter))
            {
                return new string[] { input };
            }
            return input.Split(delimiter[0]);
        }


        private static object ToArray(Type t, IList elem, string propName)
        {
            Array destination = Array.CreateInstance(t, elem.Count);
            for (int i = 0; i < elem.Count; ++i)
            {
                object value = ConvertType(propName + "[" + i + "]", elem[i], t);
                destination.SetValue(value, i);
            }
            return destination;
        }

        private static bool IsAssignableFrom(object newValue, Type requiredType)
        {
            if (newValue is MarshalByRefObject)
            {
                return true;
            }
            if (requiredType == null)
            {
                return false;
            }
            return requiredType.IsAssignableFrom(newValue.GetType());
        }

        public static GrafT Deserialize<GrafT>(System.IO.Stream stream)
        {
            System.Xml.Serialization.XmlSerializer deserializer = new System.Xml.Serialization.XmlSerializer(typeof(GrafT));
            return (GrafT) deserializer.Deserialize(stream);
        }
    }
    public class XmlDocumentConverter : TypeConverter
    {
        public override bool CanConvertFrom(
            ITypeDescriptorContext context, Type sourceType)
        {
            if (sourceType == typeof(string))
            {
                return true;
            }
            return TypeDescriptor.GetConverter(typeof(System.Xml.XmlDocument)).CanConvertFrom(context, sourceType);
            //return base.CanConvertFrom(context, sourceType);
        }

        public override object ConvertFrom(
            ITypeDescriptorContext context, CultureInfo culture, object value)
        {
            string xmlstr = value as string;
            if (xmlstr != null)
            {
                System.Xml.XmlDocument dom = new System.Xml.XmlDocument();
                if (xmlstr.Length > 0)
                {
                    dom.LoadXml(xmlstr);
                    return dom;
                }
            }
            return TypeDescriptor.GetConverter(typeof(System.Xml.XmlDocument)).ConvertFrom(context, culture, value);
        }
    }
}
